// OGoMojo
// Whitemice.MOGI.Server
// License: LGPL
// Copyright: Whitemice Consulting, 2006
// Author(s): Adam Tauno Williams (awilliam@whitemice.org)
using System;
using System.Net;
using System.Web;
using System.Collections;
using log4net;

[assembly: log4net.Config.XmlConfigurator(ConfigFileExtension="log4net",Watch=true)]
namespace Whitemice.MOGI {

	/// \brief The abstract server class to be implemented by providers
	/// \note The purpose of this class is to provided generic methods for 
	/// client applications to communicate with the server without
	/// knowledge of the RPC mechanism or transport protocols being used.
	public abstract class Server
	{
		protected	NetworkCredential	credentials;
		protected string				hostname;
		protected string				lastError;
		protected Configuration	ini;
		protected ILog					eventLog;
		protected Hashtable			supportedOperations;
		protected IAccount			currentActor;
		protected ICache				cache;
	
		/// \brief Constructor
		public Server(string logName) {
			Log = LogManager.GetLogger(logName);
			ini = new Configuration();
			cache = null;
			supportedOperations = new Hashtable();
			supportedOperations.Add(DocumentType.Account, new ArrayList());
			supportedOperations.Add(DocumentType.Contact, new ArrayList());
			supportedOperations.Add(DocumentType.Enterprise, new ArrayList());
			supportedOperations.Add(DocumentType.Task, new ArrayList());
			supportedOperations.Add(DocumentType.Appointment, new ArrayList());
			supportedOperations.Add(DocumentType.Resource, new ArrayList());
			supportedOperations.Add(DocumentType.Project, new ArrayList());
			supportedOperations.Add(DocumentType.Team, new ArrayList());
			supportedOperations.Add(DocumentType.Undefined, new ArrayList());
			FillSupportedOperations();
		}
		
		protected abstract void FillSupportedOperations();
		
		/// \brief Return true if the  object is able to communicate with the server
		/// \note This includes the ability to authenticate to the server
		public abstract bool Connected {
			get;
		}
		
		/// \brief Access the NetworkCredentials used to auth with the server
		public virtual NetworkCredential Credentials {
			get {
				Log.Debug("Credentials == ");
				return credentials;
			}
			set {
				Log.Debug("Credentials = ");
				credentials = value;
			}
		}
		
		/// \brief Accessor for the hostname of the server
		public virtual string Hostname {
			get {
				Log.Debug("Hostname == " + hostname);
				return hostname;
			}
			set {
				if (value == null)
					Log.Debug("Hostname = (null)");
					else Log.Debug("Hostname = " + value);
				hostname = value;
			}
		}
		
		/// \brief Search the server and return an ArrayList of IDocuments or NULL
		/// \param criteria The search criteria
		/// \param max The maximum number of objects to return
		/// \note In protocols like XML-RPC a max is included in the RPC parameters, in a
		/// protocol like GroupDAV it must be implemented on the client.  If the maximum
		/// is implemented on the client then the providers should stop downloading objects
		/// after than count has been reached, not download addition objects and discard them.
		/// \note Searches are expected to take one of the following forms:
		///       Document/{Account|Appointment|Contact|Enterprise|Task|Project|Resource}/{criteria}
		///         Special criteria is "Favorites", like "Document/Contact/Favorites" which will retrieve
		///         all of the documents of the specified type that are marked as favorites.
		///       Category/{Account|Appointment|Contact|Enterprise|Task|Project|Resource}/{criteria}
		///       Annotation/Document/{Account|Appointment|Contact|Enterprise|Task|Project|Resource}/{id}
		///          - the annotation criteria is effectively Annotation/{document.Name}
		///	      Relation/Document/Account|Appointment|Contact|Enterprise|Task|Project|Resource}/{id}
		///          - the relation criteria is effectively Relation/{document.Name}=
		public ArrayList Search(string criteria, int max) {
			ArrayList	results;
			
			Log.Debug("Search(" + criteria + "," + max.ToString() + ")");
			results = null;
			if (Connected) 
			{
				switch ((criteria.Split('/'))[0])
				{
					case "Document":
						if (!SupportsOperation(MOGI.DetermineType(criteria), Operation.Fetch))
							throw new NotSupportedException("Search is not supported for this document type.");
						if (criteria.Split('/')[2] == "Favorites")
							if (!SupportsOperation(MOGI.DetermineType(criteria), Operation.GetFavorites))
								throw new NotSupportedException("Favorite retrieval is not supported for this document type.");							
						results = ServerSearch(MOGI.DetermineType(criteria), ((criteria.Split('/'))[2]).Split('~'), max);
						break;
					case "Category":
						if (!SupportsOperation(MOGI.DetermineType(criteria), Operation.Categories))
							throw new NotSupportedException("Category retrieval is not supported for this document type.");
						results = ServerCategories(MOGI.DetermineType(criteria),  ((criteria.Split('/'))[2]).Split('=')[1]);
						break;
					case "Annotation":
						if (!SupportsOperation(MOGI.DetermineType(criteria), Operation.ReadAnnotations))
							throw new NotSupportedException("Annotation retrieval is not supported for this document type.");
						results = ServerAnnotations(MOGI.DetermineType(criteria),  criteria.Substring(criteria.IndexOf("/") + 1));					
						break;
					case "Relation":
						if (!SupportsOperation(MOGI.DetermineType(criteria), Operation.ListRelations))
							throw new NotSupportedException("Relation retrieval is not supported for this document type.");
						results = ServerRelations(MOGI.DetermineType(criteria), criteria.Substring(criteria.IndexOf("/") + 1));
						break;
					case "Audit":
						if (!SupportsOperation(MOGI.DetermineType(criteria), Operation.ReadAudit))
							throw new NotSupportedException("Relation retrieval is not supported for this document type.");
						results = ServerAudit(criteria.Substring(criteria.IndexOf("/") + 1));
						break;						
					default:
						throw new NotSupportedException("Search structure specified is not supported");
				}	
			} else lastError = "Search not possible unless connected, please connect.";
			return results;
		}		
		
		/// \brief Search the server and return an ArrayList of IDocuments or NULL
		/// \param criteria The search criteria
		/// \note This method implies a maximum of fifty objects will be retrieved from the server
		/// if you need more objects then call Search(criteria, max)
		public ArrayList Search(string criteria) {
			Log.Debug("Search(" + criteria + ")");
			return Search(criteria, 50);
		}
		
		/// \brief Actual searching work method called by Search(...)
		/// \note All providers should implemented this function
		protected virtual ArrayList ServerSearch(DocumentType documentType, string[] keypairs, int max)
		{
			return null;
		}
		
		/// \brief Get the specified document from the server
		/// \note All providers must implement this function
		/// \note Clients should either know what kind of document they are 
		/// going to receive and perform an appropriate case or check the Type
		/// property of the returned object
		/// \note To retrieve a document pass this method the Name of the 
		/// document.  To retrieve a new (blank) document pass the
		/// approriate document string with an id of 0, like -
		/// Document/Appointment/0.  The document will be assigned a real id
		/// when it is posted to the server.  To retrieve a new annotation
		/// object specify Annotation/Document/{type}/{id}, as in
		/// Annotation/Document/Task/11234300 - you can onlly annotate 
		/// a document AFTER it has been created on the server, you cannot
		/// annotate a blank (unsaved) document.
		public virtual IMOGIObject Get(string documentId)
		{
			Log.Debug("Get(" + documentId + ")");
			switch((documentId.Split('/'))[0])
			{
				case "Document":
					IDocument document;
					
					if(!SupportsOperation(MOGI.DetermineType(documentId), Operation.Get))
						throw new NotSupportedException("Backend does not support retrieval of this document type");
					document = (IDocument)ServerGet(documentId);
					return document;
				case "Annotation":
					switch(MOGI.Id(documentId))
					{
						case "0":
							if(!SupportsOperation(MOGI.DetermineType(documentId), Operation.MakeAnnotation))
							{
								Log.Error("Attempt at unsupported operation: GetAnnotation");
								throw new NotSupportedException(
									"Backend does not support annotation creation for this document type");
							}
							break;
						default:
							if(!SupportsOperation(MOGI.DetermineType(documentId), Operation.GetAnnotation))
							{
								Log.Error("Attempt at unsupported operation: MakeAnnotation");
								throw new NotSupportedException(
									"Backend does not support annotation retrieval for this document type");
							}
							break;
					}
					Log.Debug("Annotation action is supported according to SupportedOperation()");
					return (IDocument)GetAnnotation(documentId);
				case "Relation":
					Log.Debug("Get(Relation/...)");
					if(!SupportsOperation(MOGI.DetermineType(documentId), Operation.GetRelation))
						throw new NotSupportedException("Backend does not creating relations on this document type");
					return (IDocument)GetRelation(documentId);
			}
			return null;
		}
		
		/// \note Backends should override this method
		protected virtual IMOGIObject ServerGet(string documentId) { return null; }
		
		/// \brief Put the specified document to the server
		/// \params document The document to store to the server
		/// \note All providers must implement this function
		public void Put(IMOGIObject document)
		{
			if (document == null) 
			{
				Log.Debug("Put(!NULL!)");
				return;
			} else Log.Debug("Put(" + document.Name + ")");
			switch ((document.Name.Split('/'))[0])
			{
				case "Document":
					Log.Debug("Object to put is a document");
					if(!SupportsOperation(MOGI.DetermineType(document.Name), Operation.Put))
						throw new NotSupportedException("Backend does not support storing this document type");
					ServerPut((IDocument)document);
					if (!(cache == null))
						cache.Put((IDocument)document);
					break;
				case "Annotation":
					Log.Debug("Object to put is an annotation");
					Log.Debug("Object type bieng annotated is " + 
						MOGI.DetermineType(((IAnnotation)document).Document).ToString());
					///switch (
					if(!SupportsOperation(MOGI.DetermineType(((IAnnotation)document).Document), Operation.MakeAnnotation))
						throw new NotSupportedException("Backend does not support annotating this document type");
					PutAnnotation((IAnnotation)document);
					break;
				case "Relation":
					Log.Debug("Object to put is a relation");
					if(!SupportsOperation(MOGI.DetermineType(((IRelation)document).Source), Operation.MakeRelation))
						throw new NotSupportedException("Backend does not support relating this document type");
					PutRelation((IRelation)document);
					break;
				default:
					throw new NotSupportedException("Backend does not support storing a construct of the specified type");
			}
		}
		
		/// \brief Place holder function for putting an object to the server
		/// \param document
		protected virtual void ServerPut(IDocument document) { }
		
		/// \brief Remove the specified document from the server
		/// \params document The document to remove from the server
		public void Remove(IDocument document)
		{
			Log.Debug("Remove(" + document.Name + ")");
			Remove(document.Name);
		}
		
	
		/// \brief Remove the specified document from the server
		/// \params document The document to remove from the server
		public void Remove(string documentId)
		{
			Log.Debug("Remove(" + documentId + ")");		
			if(!SupportsOperation(MOGI.DetermineType(documentId), Operation.Remove))
				throw new NotSupportedException("Backend does not support removing this document type");			
			ServerRemove(documentId);
		}

		/// \brief Place holder function for document removal
		/// \param documentId
		/// \note Backends should override this function to support document removal		
		protected virtual void ServerRemove(string documentId)
		{
		}


		/// \brief Return an iteration of supported phone types support by server
		/// \param docType Type of document to check phone types for
		/// \note This is a wrapper around same method in the config object		
		public ICollection PhoneTypes(DocumentType docType)
		{
			Log.Debug("PhoneTypes(" + docType.ToString() + ")");
			return ini.PhoneTypes(docType);
		}

		/// \param docType The document type,  use the IDocument.Type property
		/// \param phoneType The phone type abbreviation
		/// \note This is a wrapper around same method in the config object	
		public bool SupportsPhone(DocumentType docType, string phoneType)
		{
			Log.Debug("SupportsPhone(" + docType.ToString() + "," + phoneType + ")");
			return ini.SupportsType(docType, "Phones", phoneType);
		}		

		/// \brief Return an iteration of supported address types support by server
		/// \param docType Type of document to check address types for
		/// \note This is a wrapper around same method in the config object				
		public ICollection AddressTypes(DocumentType docType)
		{
			Log.Debug("AddressTypes(" + docType.ToString() + ")");
			return ini.AddressTypes(docType);
		}
		
		/// \param docType The document type,  use the IDocument.Type property
		/// \param addressType The address type abbreviation
		/// \note This is a wrapper around same method in the config object	
		public bool SupportsAddress(DocumentType docType, string addressType)
		{
			Log.Debug("SupportsAddress(" + docType.ToString() + "," + addressType + ")");
			return ini.SupportsType(docType, "Addresses", addressType);
		}

		/// \brief Return an iteration of supported extended attributes supported by server
		/// \param docType Type of document to check for attributes
		/// \note This is a wrapper around same method in the config object		
		public ICollection ExtendedAttributes(DocumentType docType)
		{
			Log.Debug("SupportsAttribute(" + docType.ToString() + ")");
			return ini.AttributeTypes(docType);
		}
		
		/// \brief Indicates if the server supports the speccified attribute for the specified document type\//
		/// \param docType The document type,  use the IDocument.Type property
		/// \param attribute The attribute name
		/// \note This is a wrapper around same method in the config object	
		public bool SupportsAttribute(DocumentType docType, string attribute)
		{
			Log.Debug("SupportsAttribute(" + docType.ToString() + ", " + attribute + ")");
			return ini.SupportsType(docType, "Attributes", attribute);
		}
		
		/// \brief Indicates if the server supports the specified property for the specified document type
		/// \param docType The document type, use the IDocument.Type property
		/// \param property Object property name
		/// \note Currently properties aren't used for anything and aren't supported by an RPC protocol
		/// \todo File an enhancement in BugZilla to allow enumeration of object properties via XML-RPC
		public bool SupportsProperty(DocumentType docType, string property)
		{
			return false;
		}
		
		/// \brief Returns the value of a configuration directive from the server's configuration
		/// \param stanza
		/// \param directuve
		/// \note Currently this corresponds to reading the OGoMojo.ini file
		public string GetDirectiveValue(string stanza, string directive)
		{
			Log.Debug("GetDirectiveValue(" + stanza + "," + directive + ")");
			return ini.GetDirectiveValue(stanza, directive);
		}

		/// \brief Provieddes access to the log agent used by the server		
		public ILog Log
		{
			get { return eventLog; }
			set { eventLog = value; }
		}
		
		/// \brief Return true if the current user has write access
		/// \param documentId Return true/false if the current actor has write 
		/// access to the specified document
		/// \note This throws an exception if the server implmentation does not 
		//// support AccessCheck
		/// on the specified document type.  This public function calls the private 
		/// ServerAllowsWrite method.
		public bool AllowsWrite(string documentId)
		{
			Log.Debug("AllowsWrite(" + documentId + ")");
			if (SupportsOperation(MOGI.DetermineType(documentId), Operation.AccessCheck))
				return ServerAllowsWrite(documentId);
			throw new Whitemice.MOGI.NotSupportedException(
				"Checking rights on this document type not supported");
		}
		
		/// \brief Implementation method, returns true/false if current actor has 
		/// write access to the specified document.
		/// \param documentId Document to check access on
		/// \note This is called by the public method AllowsWritem,  filtering of
		/// supported operations has already occurred.
		protected virtual bool ServerAllowsWrite(string documentId) { return false; }
		
		/// \brief Return true if the current actor has read access
		/// \param documentId Document to check access on
		/// \note This method calls the private SeverAllowsRead method,  if AccessCheck
		/// is not supported on the specified document type then a NotSupportedException
		/// is thrown.
		public bool AllowsRead(string documentId)
		{
			Log.Debug("AllowsRead(" + documentId + ")");
			if (SupportsOperation(MOGI.DetermineType(documentId), Operation.AccessCheck))
				return ServerAllowsRead(documentId);
			throw new Whitemice.MOGI.NotSupportedException("Checking rights on this document type not supported");
		}
		
		/// \brief Implementation method called by AllowsRead
		/// \param documentId Document whose access should be checked
		/// \note Supported operation filtering has already occurred.
		protected virtual bool ServerAllowsRead(string documentId)
		{
			return false;
		}
		
		/// \brief Set a group of rights on an object
		/// \param documentId
		/// \param rights An array of Rights structures
		public void SetRights(string documentId, Rights[] rights)
		{
			foreach(Rights right in rights)
				SetRight(documentId, right);
		}
		
		/// \brief Set a right on an object
		/// \param documentId Docoument to set access privielege on
		/// \param rights Structure describing access rights to grant
		/// \note This public method calls the private method SetServerRight
		/// if AccessGrant is supported on the specified document type,  otherwise
		/// a NotSupportedException is thrown.
		public virtual void SetRight(string documentId, Rights right)
		{
			if (SupportsOperation(MOGI.DetermineType(documentId), Operation.AccessGrant))
			{
				ServerSetRight(documentId, right);
				return;
			}
			throw new Whitemice.MOGI.NotSupportedException("Granting rights on this document type not supported");
		}
		
		/// \brief Set a right on an object
		/// \param documentId
		/// \param rights
		public virtual void SetRight(string documentId, string actorId, bool write, bool read)
		{
			Rights	right;
			
			right.Read = read;
			right.Write = write;
			right.Holder = actorId;
			SetRight(documentId, right);
		}
		
		/// \brief Place holder function for adjusting object ACLs
		/// \note Backends which support setting of access rights must override this method
		protected virtual void ServerSetRight(string documentId, Rights right)
		{
		}
		
		/// \brief Retrieve the access rights assigned to an object
		/// \param documentId
		public ArrayList Rights(string documentId)
		{
			if (SupportsOperation(MOGI.DetermineType(documentId), Operation.AccessList))
				return ServerGetRights(documentId);				
			throw new Whitemice.MOGI.NotSupportedException("Retrieval of object rights not supported");
		}
		
		/// \brief Returns an ArrayList of Rights structures
		/// \param documentId
		protected virtual ArrayList ServerGetRights(string documentId) { return null; }
		
		/// \brief Return if actor has the privilege on the object
		/// \param actorDocumentId
		/// \param objectDocumentId
		/// \param privilege
		public virtual bool HasRight(string actorDocumentId, string objectDocumentId, string privilege)
		{
			ArrayList 	rights;
			bool 		hasRight;
			Rights		actorRights;
			
			rights = Rights(objectDocumentId);
			actorRights.Holder = null;
			actorRights.Read = false;
			actorRights.Write = false;
			if (rights == null) return true;
			hasRight = false;
			foreach(Rights right in rights)
			{
				if (MOGI.DetermineType(right.Holder) == DocumentType.Account)
				{
					if (currentActor.Name == right.Holder) actorRights = right;
				} else if (MOGI.DetermineType(right.Holder) == DocumentType.Team)
					{
						ITeam team = (ITeam)Get(right.Holder);
						if (team.Members.Contains(actorDocumentId))
						{
							if ((privilege == "r") && (right.Read)) hasRight = true;
							if ((privilege == "w") && (right.Write)) hasRight = true;							
						}
					}
			}
			if (actorRights.Holder != null)
			{
				if (privilege == "r") hasRight = actorRights.Read;
					else hasRight = actorRights.Write;
			}
			return hasRight;
		}
		
		/// \brief Returns if the backend supports the operation on the document type
		/// \param docType
		/// \param operation
		public bool SupportsOperation(DocumentType docType, Operation operation)
		{
			if (((ArrayList)supportedOperations[docType]).Contains(operation))
			{
				Log.Debug("SupportsOperation(" 
					+ docType.ToString() + ", "
					+ operation.ToString() + ") == True");
				return true;
			} 
			Log.Debug("SupportsOperation(" 
				+ docType.ToString() + ", "
				+ operation.ToString() + ") == False");
			return false;
		}
		
		/// \brief Return true if the specified object is busy inside the specified window
		/// \param documentId
		/// \param start
		/// \param end
		/// \note It is intended that clients will call this method recursiving in small
		/// (15 minutes) intervals to build free busy information about an object.
		public bool IsBusy(string documentId, DateTime start, DateTime end)
		{
			if (SupportsOperation(MOGI.DetermineType(documentId), Operation.IsBusy))
				return ServerIsBusy(documentId, start, end);				
			throw new Whitemice.MOGI.NotSupportedException("Retrieval of busy information not supported");
		}
		
		/// \brief Place holder function for free/busy information
		/// \note Backends should override this method if they support this functionality
		protected virtual bool ServerIsBusy(string documentId, DateTime start, DateTime end) { return true; }
		
		/// \brief Return the current actor object
		public IAccount CurrentActor
		{
			get { return currentActor; }
		}
		
		/// \brief Control method to dispatch category retrieval to correct implementation method
		/// \param documentType Type of document for which to retrieve categories
		/// \param criteria Pattern to match regarding categories
		/// \note The OGo server has a common set of categories for company objects, that is
		/// both Contact and Enterprise objects share the same lisr of categories.
		protected ArrayList ServerCategories(DocumentType documentType, string criteria)
		{
			ArrayList results;
			switch (documentType)
			{
				case DocumentType.Resource: results = GetResourceCategories(criteria); break;
				case DocumentType.Contact: results = GetCompanyCategories(criteria); break;
				case DocumentType.Enterprise: results = GetCompanyCategories(criteria); break;
				default: results = new ArrayList(); break;
			}
			return results;
		}
		
		/// \brief Unimplemented method to retrieve resource categories from the server
		protected virtual ArrayList GetResourceCategories(string criteria) { return new ArrayList(); }
		
		/// \brief Unimplemented method to retrieve enterprise categories from the server
		/// \note The OGo server has a common set of categories for company objects, that is
		/// both Contact and Enterprise objects share the same lisr of categories.
		protected virtual ArrayList GetCompanyCategories(string criteria) { return new ArrayList(); }
		
		/// \brief Control method to dispatch annotation retrieval to the correct implementation method
		/// \param documentType Document type for which to receive annotations
		/// \param documentId The document for which to recieve annotations
		/// \note This method is called by Search(...),  filtering for supported operations has
		/// already occurred.
		protected virtual ArrayList ServerAnnotations(DocumentType documentType, string documentId)
		{
			ArrayList results;
			
			Log.Debug("ServerAnnotations(...," + documentId + ")");
			switch (documentType)
			{
				case DocumentType.Appointment: results = GetAppointmentAnnotations(documentId); break;
				case DocumentType.Contact: results = GetContactAnnotations(documentId); break;
				case DocumentType.Enterprise: results = GetEnterpriseAnnotations(documentId); break;
				case DocumentType.Project:	results = GetProjectAnnotations(documentId); break;
				case DocumentType.Resource:	results = GetResourceAnnotations(documentId); break;
				case DocumentType.Task:	results = GetTaskAnnotations(documentId); break;
				case DocumentType.Team:	results = GetTeamAnnotations(documentId); break;
				default: results = new ArrayList(); break;
			}
			return results;
		}
		
		/// \brief Unimplemented method to retrieve appointment annotations from the server
		protected virtual ArrayList GetAppointmentAnnotations(string documentId) { return new ArrayList(); }
		/// \brief Unimplemented method to retrieve contact annotations from the server
		protected virtual ArrayList GetContactAnnotations(string documentId) { return new ArrayList(); }
		/// \brief Unimplemented method to retrieve enterprise annotations from the server
		protected virtual ArrayList GetEnterpriseAnnotations(string documentId) { return new ArrayList(); }
		/// \brief Unimplemented method to retrieve project annotations from the server		
		protected virtual ArrayList GetProjectAnnotations(string documentId) { return new ArrayList(); }
		/// \brief Unimplemented method to retrieve resource annotations from the server		
		protected virtual ArrayList GetResourceAnnotations(string documentId) {	return new ArrayList(); }
		/// \brief Unimplemented method to retrieve task annotations from the server		
		protected virtual ArrayList GetTaskAnnotations(string documentId) {	return new ArrayList(); }
		/// \brief Unimplemented method to retrieve team annotations from the server		
		protected virtual ArrayList GetTeamAnnotations(string documentId) {	return new ArrayList(); }
		
		/// \brief Store an annotation on the server
		/// \param document An IAnnotation object
		/// \note This method is called by Put(IMOGIObject).  Filtering for supported
		/// operations has already occurred.
		protected void PutAnnotation(IAnnotation document)
		{
			Log.Debug("PutAnnotation(document=" + document.Document + ")");
			switch (MOGI.DetermineType(document.Document))
			{
				case DocumentType.Account:	PutAccountAnnotation(document); break;
				case DocumentType.Appointment: PutAppointmentAnnotation(document); break;
				case DocumentType.Contact: PutContactAnnotation(document); break;
				case DocumentType.Enterprise: PutEnterpriseAnnotation(document); break;
				case DocumentType.Project: PutProjectAnnotation(document); break;
				case DocumentType.Resource: PutResourceAnnotation(document); break;
				case DocumentType.Task: PutTaskAnnotation((ITaskAnnotation)document);	break;
				case DocumentType.Team: PutTeamAnnotation(document); break;
				default:
					break;
			}
		}
		
		protected virtual void PutAccountAnnotation(IAnnotation document) { }
		protected virtual void PutAppointmentAnnotation(IAnnotation document) {	}
		protected virtual void PutContactAnnotation(IAnnotation document) {	}
		protected virtual void PutEnterpriseAnnotation(IAnnotation document) { }
		protected virtual void PutProjectAnnotation(IAnnotation document) { }
		protected virtual void PutResourceAnnotation(IAnnotation document) { }
		protected virtual void PutTaskAnnotation(ITaskAnnotation document) {}
		protected virtual void PutTeamAnnotation(IAnnotation document) { }
		
		/// \brief Return a specific annotation
		/// \param documentId The id of the annotation
		/// \note This method is called by Get(documentId),  filtering for supported
		/// operations has already occurred.
		protected virtual IAnnotation GetAnnotation(string documentId)
		{
			IAnnotation annotation;
			
			Log.Debug("GetAnnotation(" + documentId + ")");			
			switch (MOGI.DetermineType(documentId))
			{
				case DocumentType.Account: annotation = AccountAnnotation(documentId); break;
				case DocumentType.Appointment:	annotation = AppointmentAnnotation(documentId); break;
				case DocumentType.Contact:	annotation = ContactAnnotation(documentId); break;
				case DocumentType.Enterprise: annotation = EnterpriseAnnotation(documentId); break;
				case DocumentType.Project: annotation = ProjectAnnotation(documentId); break;
				case DocumentType.Resource: annotation = ResourceAnnotation(documentId);	break;
				case DocumentType.Task: annotation = TaskAnnotation(documentId); break;
				case DocumentType.Team: annotation = TeamAnnotation(documentId);	break;
				default: annotation = null; break;
			}
			return annotation;
		}
		
		protected virtual IAnnotation AccountAnnotation(string documentId) { return null; }
		protected virtual IAnnotation AppointmentAnnotation(string documentId) { return null; }
		protected virtual IAnnotation ContactAnnotation(string documentId) { return null; }
		protected virtual IAnnotation EnterpriseAnnotation(string documentId) { return null; }
		protected virtual IAnnotation ProjectAnnotation(string documentId) { return null;	}
		protected virtual IAnnotation ResourceAnnotation(string documentId) { return null; }
		protected virtual IAnnotation TaskAnnotation(string documentId) { return null; }
		protected virtual IAnnotation TeamAnnotation(string documentId) { return null; }
		
		/// \brief Returns a arraylist of relationship objects
		/// \param documentType Document type to retrieve relationships for
		/// \brief criteria See the Search(...) method for criteria structure
		/// \note This method is called by Search(...) to retrieve the relationships of
		/// an object.  Filtering on supported operations has already occured.
		protected ArrayList ServerRelations(DocumentType documentType, string criteria)
		{
			string		documentId;
			ArrayList	results;
			
			Log.Debug("ServerRelations(...," + criteria + ")");
			if (criteria.IndexOf('=') > 0)
				documentId = criteria.Substring(0, criteria.IndexOf('='));
				else documentId = criteria;
			Log.Debug("ServerRelations(...) - documentId = " + documentId);
			switch (documentType)
			{
				case DocumentType.Account: results = GetAccountRelations(documentId); break;
				case DocumentType.Appointment: results = GetAppointmentRelations(documentId); break;
				case DocumentType.Contact: results = GetContactRelations(documentId); break;
				case DocumentType.Enterprise: results = GetEnterpriseRelations(documentId); break;
				case DocumentType.Project:	results = GetProjectRelations(documentId); break;
				case DocumentType.Resource:	results = GetResourceRelations(documentId); break;
				case DocumentType.Task:	results = GetTaskRelations(documentId); break;
				case DocumentType.Team:	results = GetTeamRelations(documentId); break;
				default: results = new ArrayList(); break;
			}
			return results;
		}
		
		protected virtual ArrayList GetAccountRelations(string documentId) { return new ArrayList(); }
		protected virtual ArrayList GetAppointmentRelations(string documentId) { return new ArrayList(); }
		protected virtual ArrayList GetContactRelations(string documentId) { return new ArrayList(); }
		protected virtual ArrayList GetEnterpriseRelations(string documentId) { return new ArrayList(); }
		protected virtual ArrayList GetProjectRelations(string documentId) { return new ArrayList(); }
		protected virtual ArrayList GetResourceRelations(string documentId) { return new ArrayList(); }
		protected virtual ArrayList GetTaskRelations(string documentId) { return new ArrayList(); }
		protected virtual ArrayList GetTeamRelations(string documentId) { return new ArrayList(); }
		
		protected virtual ArrayList ServerAudit(string documentId) 
		{ return new ArrayList(); }
		
		/// \brief Call implemented server method to store a relation
		/// \param relation IRelation object 
		/// \note The Put(IMOGIObject) method calls this if the put object is an IRelation.
		/// Filtering for supported operations has already occurred at this point.
		protected void PutRelation(IRelation relation)
		{
			switch(MOGI.DetermineType(relation.Source))
			{
				case DocumentType.Account:	PutAccountRelation(relation); break;
				case DocumentType.Appointment: PutAppointmenttRelation(relation); break;
				case DocumentType.Contact: PutContactRelation(relation); break;
				case DocumentType.Enterprise: PutEnterpriseRelation(relation); break;
				case DocumentType.Project: PutProjectRelation(relation); break;
				case DocumentType.Resource: PutResourceRelation(relation); break;
				case DocumentType.Task: PutTaskRelation(relation); break;
				case DocumentType.Team: PutTeamRelation(relation); break;			
			}
		}
		
		protected virtual void PutAccountRelation(IRelation relation) { }
		protected virtual void PutAppointmenttRelation(IRelation relation) { }
		protected virtual void PutContactRelation(IRelation relation) { }
		protected virtual void PutEnterpriseRelation(IRelation relation) { }
		protected virtual void PutProjectRelation(IRelation relation) { }
		protected virtual void PutResourceRelation(IRelation relation) { }
		protected virtual void PutTaskRelation(IRelation relation) { }
		protected virtual void PutTeamRelation(IRelation relation) { }
		
		/// \brief Get a specific relation if it exists
		/// \param documentId
		/// \todo Implement
		protected IRelation GetRelation(string documentId)
		{
			return null;
		}

		/// \brief Return or set the server's cache object
		/// \warn Changing cache objects during operations is an almost certain
		/// way to introduce inconsistencies between the data presented to the
		/// user and the data on which the server is operating.
		public ICache Cache
		{
			get { return cache; }
			set { cache = value; }
		}
		
		/// \brief Return the name of a document when all you have is the primary key
		/// \param primaryKey
		public string GetName(string primaryKey)
		{
			Log.Debug("GetName(" + primaryKey + ")");
			if (SupportsOperation(DocumentType.Undefined, Operation.GetName))
				return ServerGetName(primaryKey);				
			Log.Info("Retrieval of name for primary key is not supported");
			throw new Whitemice.MOGI.NotSupportedException("Retrieval of name for primary key is not supported");
		}
		
    protected virtual string ServerGetName(string primaryKey) { return null; }		
	}
}